import json
import lark_oapi as lark
from lark_oapi.api.docx.v1 import RawContentDocumentRequest, RawContentDocumentResponse
from lark_oapi.api.drive.v1 import ListFileRequest, ListFileCommentRequest, PatchFileCommentRequest, PatchFileCommentRequestBody
from lark_oapi.api.sheets.v3 import QuerySpreadsheetSheetRequest
import requests
from typing import List, Dict, Any, Optional
from fastapi import FastAPI, HTTPException, BackgroundTasks
from config import settings
import uvicorn
import asyncio
from pydantic import BaseModel
import re
import os  # 新增：用于从环境变量动态获取端口，避免硬编码和交互
import logging
from feishu_bitable_processor import process_feishu_record_cell
# 配置logger
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# 创建FastAPI应用
app = FastAPI(title="飞书电子表格评论处理API", version="1.0.0")


class FolderRequest(BaseModel):
    folder_token: str


class SheetRequest(BaseModel):
    sheet_token: str
    ProductHighlights: Optional[str] = None
    record_id: Optional[str] = None
    record_link: Optional[str] = None


# ------------------------------
# FeishuSheet、FeishuSheetCommentProcessor类逻辑不变（省略重复代码，保持原功能）
# ------------------------------
class FeishuSheet:
    def __init__(self, app_id: str, app_secret: str):
        self.app_id = app_id
        self.app_secret = app_secret
        self.access_token = None
        self.base_api_url = "https://open.feishu.cn/open-apis"
        self.SPREADSHEET_TOKEN_PATTERN = re.compile(r'[a-zA-Z0-9]{28,36}')
        self.SHEET_ID_PATTERN = re.compile(r'^[a-zA-Z0-9_-]{6,10}$')

    def get_access_token(self) -> Optional[str]:
        url = f"{self.base_api_url}/auth/v3/tenant_access_token/internal/"
        params = {"app_id": self.app_id, "app_secret": self.app_secret}
        try:
            response = requests.post(url, json=params, timeout=10)
            response.raise_for_status()
            result = response.json()
            if result.get("code") == 0:
                self.access_token = result["tenant_access_token"]
                return self.access_token
            else:
                logger.error(f"飞书令牌获取失败，错误码：{result.get('code')}，信息：{result.get('msg')}")
                return None
        except requests.exceptions.RequestException as e:
            logger.error(f"飞书令牌请求失败：{str(e)}")
            return None

    def get_sheet_ids(self, spreadsheet_token: str) -> List[Dict]:
        client = lark.Client.builder().app_id(self.app_id).app_secret(self.app_secret).log_level(lark.LogLevel.DEBUG).build()
        request = QuerySpreadsheetSheetRequest.builder().spreadsheet_token(spreadsheet_token).build()
        response = client.sheets.v3.spreadsheet_sheet.query(request)
        if not response.success():
            logger.error(f"获取工作表信息失败: {response.code}, {response.msg}")
            return []
        sheets_info = []
        if response.data and hasattr(response.data, 'sheets'):
            for sheet in response.data.sheets:
                if hasattr(sheet, 'sheet_id'):
                    sheets_info.append({"sheet_id": sheet.sheet_id, "title": getattr(sheet, 'title', '')})
        return sheets_info

    def get_cell_value(self, spreadsheet_token: str, sheet_id: str, cell_range: str) -> Optional[str]:
        access_token = self.get_access_token()
        if not access_token:
            logger.error("无法获取飞书访问令牌")
            return None
        full_range = f"{sheet_id}!{cell_range}" if ":" in cell_range else f"{sheet_id}!{cell_range}:{cell_range}"
        url = f"{self.base_api_url}/sheets/v2/spreadsheets/{spreadsheet_token}/values/{full_range}"
        headers = {"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}
        try:
            response = requests.get(url, headers=headers, timeout=10)
            response.raise_for_status()
            result = response.json()
            if result.get("code") == 0:
                values = result.get("data", {}).get("valueRange", {}).get("values", [])
                return values[0][0] if (values and len(values) > 0 and len(values[0]) > 0) else ""
            else:
                logger.error(f"获取单元格值失败，错误码：{result.get('code')}，信息：{result.get('msg')}")
                return None
        except requests.exceptions.RequestException as e:
            logger.error(f"获取单元格值请求失败：{str(e)}")
            return None

    def update_cell_value(self, spreadsheet_token: str, sheet_id: str, cell_range: str, value: str) -> bool:
        access_token = self.get_access_token()
        if not access_token:
            logger.error("无法获取飞书访问令牌")
            return False
        full_range = f"{sheet_id}!{cell_range}" if ":" in cell_range else f"{sheet_id}!{cell_range}:{cell_range}"
        url = f"{self.base_api_url}/sheets/v2/spreadsheets/{spreadsheet_token}/values"
        headers = {"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}
        data = {"valueRange": {"range": full_range, "values": [[value]]}}
        try:
            response = requests.put(url, headers=headers, json=data, timeout=10)
            response.raise_for_status()
            return response.json().get("code") == 0
        except requests.exceptions.RequestException as e:
            logger.error(f"更新单元格值请求失败：{str(e)}")
            return False


class FeishuSheetCommentProcessor:
    def __init__(self, app_id: str, app_secret: str):
        self.client = lark.Client.builder().app_id(app_id).app_secret(app_secret).log_level(lark.LogLevel.INFO).build()
        self.feishu_sheet = FeishuSheet(app_id, app_secret)

    def get_folder_sheets(self, folder_token: str) -> List[Dict]:
        sheets = []
        page_token = None
        while True:
            request = ListFileRequest.builder().page_size(50).folder_token(folder_token).order_by("EditedTime").direction("DESC").user_id_type("open_id").build()
            response = self.client.drive.v1.file.list(request)
            if not response.success():
                logger.error(f"获取文件夹文件失败: {response.msg}")
                break
            if not (response.data and hasattr(response.data, 'files')):
                break
            for item in response.data.files:
                # 保存文件类型信息，以便后续处理时使用
                if item.type in ["sheet", "bitable"]:
                    sheets.append({
                        "token": item.token, 
                        "name": item.name, 
                        "type": item.type,  # 保存文件类型
                        "url": f"https://sample.feishu.cn/{item.type}s/{item.token}"
                    })
            if not (hasattr(response.data, 'has_more') and response.data.has_more):
                break
            page_token = response.data.page_token if (hasattr(response.data, 'page_token') and response.data.page_token) else None
            if not page_token:
                break
        return sheets

    def get_sheet_comments(self, spreadsheet_token: str, file_type: str = "sheet") -> List[Dict]:
        comments = []
        page_token = None
        while True:
            # 根据文件类型使用正确的参数
            builder = ListFileCommentRequest.builder().file_token(spreadsheet_token).file_type(file_type).is_whole(True).page_size(50).user_id_type("open_id")
            if page_token:
                builder.page_token(page_token)
            request = builder.build()
            response = self.client.drive.v1.file_comment.list(request)
            # 移除可能的交互打印（若之前有等待确认的逻辑，此处已清理）
            if hasattr(response, 'raw') and hasattr(response.raw, 'content'):
                try:
                    content_str = response.raw.content.decode('utf-8')
                    content_json = json.loads(content_str)
                    logger.info(f"评论API返回原始数据: {json.dumps(content_json, ensure_ascii=False, indent=2)}")
                except Exception as e:
                    logger.error(f"解析评论API响应失败: {str(e)}")
            if not response.success():
                logger.error(f"获取电子表格评论失败: {response.msg}")
                break
            if not (response.data and hasattr(response.data, 'items')):
                break
            for comment in response.data.items:
                if comment.is_solved:
                    continue
                if hasattr(comment, 'reply_list') and hasattr(comment.reply_list, 'replies'):
                    for reply in comment.reply_list.replies:
                        comments.append({
                            "comment_id": comment.comment_id,
                            "reply_id": reply.reply_id,
                            "quote": getattr(comment, 'quote', ''),
                            "text": self._extract_reply_text(reply),
                            "spreadsheet_token": spreadsheet_token
                        })
            if not (hasattr(response.data, 'has_more') and response.data.has_more):
                break
            page_token = response.data.page_token if (hasattr(response.data, 'page_token') and response.data.page_token) else None
            if not page_token:
                break
        return comments
    def create_block(self, doc_token: str, block_id: str, index: int, content: str ):
        """
        在指定文档的指定块下创建子块
        
        Args:
            doc_token (str): 文档token
            block_id (str): 父级块的block_id
            index (int): 插入位置索引
            content (str): 要插入的内容
            
        Returns:
            dict: API响应结果
        """
        access_token = self.feishu_sheet.get_access_token()
        if not access_token:
            logger.error("无法获取飞书访问令牌")
            return None
            
        url = f"{self.feishu_sheet.base_api_url}/docx/v1/documents/{doc_token}/blocks/{block_id}/children"
        headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json"
        }
        
        # 构造请求数据
        data = {
            "index": index,
            "children": [
                {
                    "block_type": 2,  # 段落块类型
                    "text": {
                        "elements": [
                            {
                                "text_run": {
                                    "content": content
                                }
                            }
                        ]
                    }
                }
            ]
        }
        
        try:
            response = requests.post(url, headers=headers, json=data)
            response.raise_for_status()
            result = response.json()
            
            if result.get("code") == 0:
                logger.info("创建块成功")
                return result
            else:
                logger.error(f"创建块失败，错误码：{result.get('code')}，信息：{result.get('msg')}")
                return None
                
        except requests.exceptions.RequestException as e:
            logger.error(f"创建块请求失败：{str(e)}")
            return None
    
    def get_block_content(self, doc_token: str, block_id: str):
        """
        获取指定文档中特定块的内容
        
        Args:
            doc_token (str): 文档token
            block_id (str): 块的block_id
            
        Returns:
            dict: API响应结果，包含块的详细信息
        """
        access_token = self.feishu_sheet.get_access_token()
        if not access_token:
            logger.error("无法获取飞书访问令牌")
            return None
            
        # 检查参数有效性
        if not doc_token or not block_id:
            logger.error("文档token或块ID不能为空")
            return None
            
        url = f"{self.feishu_sheet.base_api_url}/docx/v1/documents/{doc_token}/blocks/{block_id}"
        headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json"
        }
        
        logger.debug(f"获取块内容，URL: {url}")
        
        try:
            response = requests.get(url, headers=headers)
            response.raise_for_status()
            result = response.json()
            
            if result.get("code") == 0:
                logger.info("获取块内容成功")
                return result.get("data", {}).get("block", {})
            else:
                logger.error(f"获取块内容失败，错误码：{result.get('code')}，信息：{result.get('msg')}")
                return None
                
        except requests.exceptions.RequestException as e:
            logger.error(f"获取块内容请求失败：{str(e)}")
            return None
    
    def get_all_block_content(self, doc_token: str) -> tuple[str]:
        """
        获取文档的block_id列表
        
        Args:
            doc_token (str): 文档token
            
        Returns:
            tuple[str, dict]: 文档内容文本和第一个块的完整信息
        """
        access_token = self.feishu_sheet.get_access_token()
        if not access_token:
            logger.error("无法获取飞书访问令牌")
            return "", {}
        
        # 构造请求URL
        url = f"{self.feishu_sheet.base_api_url}/docx/v1/documents/{doc_token}/blocks"
        headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json"
        }
        
        try:
            response = requests.get(url, headers=headers, timeout=30)
            response.raise_for_status()
            result = response.json()
            
            if result.get("code") == 0:
                data = result.get("data", {})
                items = data.get("items", [])
                text_parts = []
                
                # 检查是否有块内容
                if not items:
                    return "", {}
                
                for block in items:
                    # 根据飞书文档API结构，处理不同类型的块
                    block_type = block.get("block_type")
                    
                    # 处理段落块
                    if block_type == 2:  # paragraph
                        block_content = ""
                        for element in block["text"].get("elements", []):
                            if "text_run" in element:
                                content = element["text_run"].get("content", "")
                                if content:
                                    block_content += content
                        if block_content:
                            text_parts.append(block_content)    
                    # 处理标题块
                    elif block_type in [3, 4, 5, 6, 7, 8, 9, 10, 11]:  # heading blocks
                        if "heading" + str(block_type-2) in block:
                            heading = block["heading" + str(block_type-2)]
                            if "elements" in heading:
                                elements = heading["elements"]
                                for element in elements:
                                    if "text_run" in element:
                                        content = element["text_run"].get("content", "")
                                        if content:
                                            text_parts.append(content)
                    elif block_type == 12:
                        block_content = ""
                        for element in block["bullet"].get("elements", []):
                            if "text_run" in element:
                                content = element["text_run"].get("content", "")
                                if content:
                                    block_content += content
                        if block_content:
                            text_parts.append(block_content)
                    elif block_type == 13:
                        block_content = ""
                        for element in block["ordered"].get("elements", []):
                            if "text_run" in element:
                                content = element["text_run"].get("content", "")
                                if content:
                                    block_content += content
                        if block_content:
                            text_parts.append(block_content) 
                return "\n\n".join(text_parts), items
            else:
                logger.error(f"获取文档所有块失败，错误码：{result.get('code')}，信息：{result.get('msg')}")
                return "", {}
                
        except requests.exceptions.RequestException as e:
            logger.error(f"获取文档所有块请求失败：{str(e)}")
            return "", {}
    
    def get_obj_token(self, doc_token: str):
        # 确保访问令牌有效
        if not self.feishu_sheet.access_token:
            logger.info("访问令牌不存在，正在获取新的访问令牌...")
            if not self.feishu_sheet.get_access_token():
                logger.error("无法获取访问令牌")
                raise Exception("无法获取访问令牌")
        
        # 使用 requests 库替代 Lark SDK 发送 HTTP GET 请求
        url = "https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node"
        headers = {
            "Authorization": f"Bearer {self.feishu_sheet.access_token}"
        }
        params = {
            "token": doc_token
        }
        
        logger.info(f"正在获取文档节点信息，token: {doc_token}")
        response = requests.get(url, headers=headers, params=params)
        
        # 如果是令牌过期导致的401错误，尝试重新获取令牌
        if response.status_code == 401:
            logger.info("访问令牌可能已过期，尝试重新获取...")
            if self.feishu_sheet.get_access_token():
                headers["Authorization"] = f"Bearer {self.feishu_sheet.access_token}"
                response = requests.get(url, headers=headers, params=params)
        
        if response.status_code != 200:
            logger.error(f"获取文档节点信息失败，状态码: {response.status_code}，响应内容: {response.text}")
            response.raise_for_status()
            
        result = response.json()
        logger.info(f"API调用结果: {result}")
        
        # 检查API调用是否成功
        if result.get("code") != 0:
            error_code = result.get("code")
            error_msg = result.get("msg")
            logger.error(f"获取文档节点信息失败，错误码: {error_code}，错误信息: {error_msg}")
            raise Exception(f"获取文档节点信息失败，错误码: {error_code}，错误信息: {error_msg}")
        
        # 返回 node 中的 obj_token
        node_data = result.get("data", {}).get("node", {})
        obj_token = node_data.get("obj_token")
        
        if not obj_token:
            logger.error("无法从响应中提取 obj_token")
            raise Exception("无法从响应中提取 obj_token")
            
        logger.info(f"成功获取 obj_token: {obj_token}")
        return obj_token
    

    def delete_doc_content(self, doc_token: str, block_id: str, start_index: int, end_index: int) -> bool:
        """
        删除指定文档中块的子内容
        
        Args:
            doc_token (str): 文档token
            block_id (str): 父块的block_id（不是文档ID）
            start_index (int): 开始索引
            end_index (int): 结束索引
            
        Returns:
            bool: 删除成功返回True，否则返回False
        """
        access_token = self.feishu_sheet.get_access_token()
        if not access_token:
            logger.error("无法获取飞书访问令牌")
            return False
            
        # 检查参数有效性
        if not doc_token or not block_id:
            logger.error("文档token或块ID不能为空")
            return False
            
        # 使用飞书文档删除API，删除指定块的子内容
        url = f"{self.feishu_sheet.base_api_url}/docx/v1/documents/{doc_token}/blocks/{block_id}/children/batch_delete"
        headers = {
            "Authorization": f"Bearer {access_token}", 
            "Content-Type": "application/json; charset=utf-8"
        }
        
        # 构造删除块内容的请求体
        data = {
            "start_index": start_index,
            "end_index": end_index
        }
        
        logger.debug(f"删除文档内容，URL: {url}，数据: {data}")
        
        max_retries = 3
        retry_count = 0
        while retry_count < max_retries:
            try:
                response = requests.delete(url, headers=headers, json=data, timeout=30)
                response.raise_for_status()
                result = response.json()
                
                if result.get("code") == 0:
                    logger.info("文档内容删除成功")
                    return True
                else:
                    logger.error(f"删除文档内容失败，错误码：{result.get('code')}，信息：{result.get('msg')}")
                    return False
                    
            except requests.exceptions.RequestException as e:
                logger.error(f"删除文档内容请求失败：{str(e)}")
                retry_count += 1
                logging.warning(f"第{retry_count}次重试，错误: {str(e)}")
                if retry_count >= max_retries:
                    logging.error(f"达到最大重试次数{max_retries}，返回错误")
                    return False

    def get_doc_comments(self, doc_token: str, file_type: str) -> List[Dict]:
        """获取文档类型文件的评论"""
        comments = []
        page_token = None
        while True:
            # 根据文件类型使用正确的参数
            builder = ListFileCommentRequest.builder().file_token(doc_token).file_type(file_type).is_whole(True).page_size(50).user_id_type("open_id")
            if page_token:
                builder.page_token(page_token)
            request = builder.build()
            response = self.client.drive.v1.file_comment.list(request)
            if not response.success():
                logger.error(f"获取文档评论失败: {response.msg}")
                break
            if not (response.data and hasattr(response.data, 'items')):
                break
            for comment in response.data.items:
                if comment.is_solved:
                    continue
                if hasattr(comment, 'reply_list') and hasattr(comment.reply_list, 'replies'):
                    for reply in comment.reply_list.replies:
                        comments.append({
                            "comment_id": comment.comment_id,
                            "reply_id": reply.reply_id,
                            "quote": getattr(comment, 'quote', ''),
                            "text": self._extract_reply_text(reply),
                            "doc_token": doc_token
                        })
            if not (hasattr(response.data, 'has_more') and response.data.has_more):
                break
            page_token = response.data.page_token if (hasattr(response.data, 'page_token') and response.data.page_token) else None
            if not page_token:
                break
        return comments

    def delete_doc_comments(self, doc_token: str, file_type: str) -> bool:
        """删除文档类型文件的评论"""
        try:
            comments = self.get_doc_comments(doc_token, file_type)
            reply_comments = [c for c in comments if c['reply_id']]
            logger.info(f"找到 {len(reply_comments)} 条回复评论需要删除")
            
            deleted_count = 0
            failed_count = 0
            
            for comment in reply_comments:
                try:
                    # 构建删除请求
                    request = PatchFileCommentRequest.builder()\
                        .file_token(doc_token)\
                        .comment_id(comment['comment_id'])\
                        .file_type(file_type)\
                        .request_body(PatchFileCommentRequestBody.builder()
                            .is_solved(True)
                            .build()) \
                        .build()
                    
                    response = self.client.drive.v1.file_comment.patch(request)
                    
                    if not response.success():
                        logger.error(f"删除回复评论失败: {response.msg}, code: {response.code}")
                        failed_count += 1
                    else:
                        logger.info(f"已删除回复: {comment['reply_id']}")
                        deleted_count += 1
                        
                except Exception as e:
                    logger.error(f"删除回复时出错: {str(e)}")
                    failed_count += 1
            
            logger.info(f"评论删除完成，成功: {deleted_count}, 失败: {failed_count}")
            # 即使有部分失败，也返回True，因为我们已经尽力处理了
            return failed_count == 0 or deleted_count > 0
            
        except Exception as e:
            logger.error(f"删除文档评论时发生错误: {str(e)}")
            return False

    def _extract_comment_text(self, comment) -> str:
        text = ''
        if hasattr(comment, 'content') and hasattr(comment.content, 'elements'):
            for element in comment.content.elements:
                if hasattr(element, 'text_run') and hasattr(element.text_run, 'text'):
                    text += element.text_run.text
        return text

    def _extract_reply_text(self, reply) -> str:
        text = ''
        if hasattr(reply, 'content') and hasattr(reply.content, 'elements'):
            for element in reply.content.elements:
                if hasattr(element, 'text_run') and hasattr(element.text_run, 'text'):
                    text += element.text_run.text
        return text

    def delete_all_comments(self, spreadsheet_token: str) -> bool:
        try:
            comments = self.get_sheet_comments(spreadsheet_token)
            reply_comments = [c for c in comments if c['reply_id']]
            logger.info(f"找到 {len(reply_comments)} 条回复评论需要删除")
            for comment in reply_comments:
                try:
                    request = PatchFileCommentRequest.builder()\
                        .file_token(spreadsheet_token)\
                        .comment_id(comment['comment_id'])\
                        .file_type("sheet")\
                        .request_body(PatchFileCommentRequestBody.builder()
                            .is_solved(True)
                            .build()) \
                        .build()
                    
                    response = self.client.drive.v1.file_comment.patch(request)
                    if not response.success():
                        logger.error(f"删除回复评论失败: {response.msg}")
                    else:
                        logger.info(f"已删除回复: {comment['reply_id']}")
                except Exception as e:
                    logger.error(f"删除回复时出错: {str(e)}")
            return True
        except Exception as e:
            logger.error(f"删除评论时发生错误: {str(e)}")
            return False

    def extract_cell_range_from_quote(self, quote: str) -> Optional[str]:
        cell_ref = quote[:3].strip() if len(quote) >= 3 else ""
        if cell_ref and cell_ref[-1] == ' ':
            cell_ref = cell_ref[:-1]
        return cell_ref if cell_ref else None


def call_revision_api(content: str, cell_content: str, comment: Dict, ProductHighlights: Optional[str]) -> Optional[str]:
    try:
        data = {"content": content, "quote1": cell_content, "text1": comment.get("text", ""), "ProductHighlights":ProductHighlights}
        logger.info(f"调用改稿API，数据: {json.dumps(data, ensure_ascii=False)}")
        response = requests.post(settings.revision_api_url, json=data, timeout=300)
        if response.status_code == 200:
            result = response.json()
            logger.info(f"改稿API返回: {json.dumps(result, ensure_ascii=False)}")
            return result.get("revision_result", "")
        else:
            logger.error(f"改稿API调用失败: {response.status_code} - {response.text}")
            return None
    except Exception as e:
        logger.error(f"改稿API调用异常: {str(e)}")
        return None


def call_revision_api_doc(content: str, comment: Dict, ProductHighlights:Optional[str]) -> Optional[str]:
    try:
        data = {"content": content, **(comment), "ProductHighlights": ProductHighlights}
        logger.info(f"调用改稿API，数据: {json.dumps(data, ensure_ascii=False)}")
        response = requests.post(settings.revision_api_url, json=data, timeout=300)
        if response.status_code == 200:
            result = response.json()
            logger.info(f"改稿API返回: {json.dumps(result, ensure_ascii=False)}")
            return result.get("revision_result", "")
        else:
            logger.error(f"改稿API调用失败: {response.status_code} - {response.text}")
            return None
    except Exception as e:
        logger.error(f"改稿API调用异常: {str(e)}")
        return None

# ------------------------------
# 关键改动1：修改API路径为 /api/process-sheet-comments（单处修改）
# ------------------------------
@app.post("/api/process-sheet-comments")
async def process_sheet_comments(request: FolderRequest, background_tasks: BackgroundTasks):
    try:
        processor = FeishuSheetCommentProcessor(
            app_id=settings.feishu_app_id,
            app_secret=settings.feishu_app_secret
        )
        background_tasks.add_task(process_sheets_comments, processor, request.folder_token)
        return {
            "status": "success",
            "message": "电子表格评论处理任务已开始在后台执行",
            "folder_token": request.folder_token
        }
    except Exception as e:
        logger.error(f"触发处理失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"触发处理失败: {str(e)}")


@app.post("/api/process-single-sheet")
async def process_single_sheet_comments(request: SheetRequest, background_tasks: BackgroundTasks):
    try:
        processor = FeishuSheetCommentProcessor(
            app_id=settings.feishu_app_id,
            app_secret=settings.feishu_app_secret
        )
        background_tasks.add_task(process_single_sheet, processor, request.sheet_token, request.record_link, request.ProductHighlights)
        return {
            "status": "success",
            "message": "电子表格评论处理任务已开始在后台执行",
            "sheet_token": request.sheet_token
        }
    except Exception as e:
        logger.error(f"触发处理失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"触发处理失败: {str(e)}")


async def process_sheets_comments(processor: FeishuSheetCommentProcessor, folder_token: str):
    try:
        logger.info(f"开始处理文件夹 {folder_token} 下的电子表格...")
        sheets = processor.get_folder_sheets(folder_token)
        logger.info(f"找到 {len(sheets)} 个电子表格")
        if not sheets:
            logger.info("未找到可处理的电子表格")
            return
        for sheet in sheets:
            logger.info(f"\n处理电子表格: {sheet['name']}")
            # 根据文件类型传递正确的file_type参数
            file_type = "sheet" if sheet['type'] == "sheet" else "bitable"
            all_comments = processor.get_sheet_comments(sheet['token'], file_type)
            logger.info(f"找到 {len(all_comments)} 条评论")
            if not all_comments:
                logger.info("该电子表格没有评论，跳过")
                continue
            sheets_info = processor.feishu_sheet.get_sheet_ids(sheet['token'])
            if not sheets_info:
                logger.info("无法获取工作表信息，跳过")
                continue
            logger.info(f"工作表信息: {sheets_info}")
            sheet_id = sheets_info[0]['sheet_id']
            logger.info(f"使用工作表ID: {sheet_id}")
            cell_comments = {}
            for comment in all_comments:
                cell_range = processor.extract_cell_range_from_quote(comment['quote'])
                if not cell_range:
                    logger.info(f"无法从引用 '{comment['quote']}' 提取单元格范围，跳过")
                    continue
                if cell_range not in cell_comments:
                    cell_comments[cell_range] = []
                cell_comments[cell_range].append(comment)
            for cell_range, comments in cell_comments.items():
                if ":" not in cell_range:
                    cell_range = f"{cell_range}:{cell_range}"
                original_text = processor.feishu_sheet.get_cell_value(sheet['token'], sheet_id, cell_range)
                if original_text is None:
                    logger.info(f"无法读取单元格 {cell_range} 的内容，跳过")
                    continue
                logger.info(f"处理单元格 {cell_range}: {original_text[:50]}...")
                logger.info(f"找到 {len(comments)} 条相关评论")
                valid_comments = [c for c in comments if c.get("text")]
                if valid_comments:
                    comment = valid_comments[0]
                    revision_result = call_revision_api(original_text, comment)
                    if not revision_result:
                        logger.info("改稿失败，跳过此单元格")
                        continue
                    update_success = processor.feishu_sheet.update_cell_value(sheet['token'], sheet_id, cell_range, revision_result)
                    if not update_success:
                        logger.info("更新单元格失败，跳过")
                        continue
                    logger.info(f"单元格更新成功: {revision_result[:50]}...")
                else:
                    logger.info("没有找到有效评论（有文本内容），跳过此单元格")
            logger.info("删除所有评论...")
            delete_success = processor.delete_all_comments(sheet['token'])
            logger.info("评论删除成功" if delete_success else "评论删除失败")
        logger.info("\n所有电子表格处理完成")
    except Exception as e:
        logger.error(f"处理过程中发生错误: {str(e)}")
        import traceback
        traceback.print_exc()


# ------------------------------
# 关键改动2：健康检查端点保持纯净，无任何交互和端口检测逻辑
# ------------------------------
@app.get("/health")
async def health_check():
    """健康检查端点：仅返回服务状态，无任何交互和端口检测"""
    return {"status": "healthy", "service": "sheet-comment-processor"}


async def process_single_sheet(processor: FeishuSheetCommentProcessor, token: str, record_link: Optional[str], ProductHighlights: Optional[str]):
    try:
        # 兼容处理传入的token参数，支持完整URL和单纯token两种格式
        if token.startswith("http"):
            # 从URL中提取token部分
            # 支持多种格式：
            # 1. 完整路径格式：https://dkke3lyh7o.feishu.cn/sheets/DfOVsyID6hegWFtXRToc0I0nnUd
            # 2. 域名格式：http://VPRIdiNgvorqD7xBDxOcOkZgnCe
            # 3. wiki格式：https://dkke3lyh7o.feishu.cn/wiki/Y1uPwrp2SiHbIEk4ga6cM3Buneg
            # 4. 带查询参数的格式：https://xxx.feishu.cn/wiki/abc?from=other
            
            import re
            # 使用一个正则表达式同时提取URL类型和token
            # group(1) = URL类型 (wiki, sheets, docx等)，group(2) = token
            url_pattern = r'https?://[^/]+/(?:([^/?#]+)/)?([a-zA-Z0-9]+(?:[a-zA-Z0-9_\-]*)?)(?:\.[a-zA-Z]+)?(?:\?.*)?$'
            match = re.search(url_pattern, token)
            
            if match:
                url_type = match.group(1)  # URL类型，如 "wiki", "sheets" 等
                extracted_token = match.group(2)  # 提取到的token
                
                logger.info(f"从URL中提取信息: 类型={url_type}, token={extracted_token}")
                
                if url_type == "wiki":
                    # Wiki类型URL处理
                    logger.info(f"检测到wiki类型链接，正在获取实际文档token...")
                    try:
                        # 调用get_obj_token函数获取实际的obj_token
                        sheet_token = processor.get_obj_token(extracted_token)
                        logger.info(f"成功获取wiki文档的实际token: {sheet_token}")
                    except Exception as e:
                        logger.error(f"获取wiki文档token失败: {str(e)}")
                        raise
                else:
                    # 其他类型URL处理
                    sheet_token = extracted_token
                    logger.info(f"使用提取的token: {sheet_token}")
            else:
                sheet_token = token
                logger.warning(f"无法从URL中提取token，使用原始值: {token}")
        else:
            # 直接使用token
            sheet_token = token
            logger.info(f"使用直接传入的token: {sheet_token}")

        logger.info(f"开始处理文档 {sheet_token}...")
        
        # 默认文件类型
        file_type = "sheet"  # 默认类型
        
        # 尝试通过不同类型的评论API来判断文件类型
        client = lark.Client.builder().app_id(settings.feishu_app_id).app_secret(settings.feishu_app_secret).log_level(lark.LogLevel.DEBUG).build()
        file_types_to_try = ["sheet", "doc", "docx"]
        
        for ft in file_types_to_try:
            try:
                comment_request = ListFileCommentRequest.builder().file_token(sheet_token).file_type(ft).page_size(1).user_id_type("open_id").build()
                comment_response = client.drive.v1.file_comment.list(comment_request)
                if comment_response.success():
                    file_type = ft
                    logger.info(f"通过评论API确定文件类型: {file_type}")
                    break
                elif "file_type" in str(comment_response.msg).lower() or "invalid" in str(comment_response.msg).lower():
                    # 如果是因为file_type错误导致的失败，尝试下一个类型
                    continue
                else:
                    # 其他错误，记录但继续尝试
                    logger.debug(f"尝试文件类型 {ft} 失败: {comment_response.msg}")
            except Exception as e:
                logger.debug(f"尝试文件类型 {ft} 时出错: {str(e)}")
                continue
        
        logger.info(f"最终确定文件类型: {file_type}")
        
        # 根据文件类型采用不同的处理方式
        if file_type in ["sheet", "bitable"]:
            # 处理电子表格类型文档
            await process_sheet_document(processor, sheet_token, file_type, ProductHighlights)
            if record_link:
                try:

                    await process_feishu_record_cell(record_link, "改后结果", "https://dkke3lyh7o.feishu.cn/" + file_type + "/" + sheet_token)
                    logger.info(f"Successfully updated Feishu bitable record with error for record_link: {record_link}")
                except Exception as e:
                    logger.error(f"Failed to update Feishu bitable record with error for record_link {record_link}: {str(e)}")
        elif file_type in ["doc", "docx"]:
            # 处理文档类型文档
            await process_doc_document(processor, sheet_token, file_type, ProductHighlights)
            if record_link:
                try:

                    await process_feishu_record_cell(record_link, "改后结果", "https://dkke3lyh7o.feishu.cn/" + file_type + "/" + sheet_token)
                    logger.info(f"Successfully updated Feishu bitable record with error for record_link: {record_link}")
                except Exception as e:
                    logger.error(f"Failed to update Feishu bitable record with error for record_link {record_link}: {str(e)}")
        else:
            logger.info(f"不支持的文件类型: {file_type}，跳过处理")
       
    except Exception as e:
        logger.error(f"处理过程中发生错误: {str(e)}")
        import traceback
        traceback.print_exc()


async def process_sheet_document(processor: FeishuSheetCommentProcessor, sheet_token: str, file_type: str, ProductHighlights: Optional[str]):
    """处理电子表格类型文档"""
    try:
        all_comments = processor.get_sheet_comments(sheet_token, file_type)
        logger.info(f"找到 {len(all_comments)} 条评论")
        if not all_comments:
            logger.info("该电子表格没有评论，跳过")
            return
            
        sheets_info = processor.feishu_sheet.get_sheet_ids(sheet_token)
        if not sheets_info:
            logger.info("无法获取工作表信息，跳过")
            return
        logger.info(f"工作表信息: {sheets_info}")
        sheet_id = sheets_info[0]['sheet_id']
        logger.info(f"使用工作表ID: {sheet_id}")
        
        cell_comments = {}
        for comment in all_comments:
            cell_range = processor.extract_cell_range_from_quote(comment['quote'])
            if not cell_range:
                logger.info(f"无法从引用 '{comment['quote']}' 提取单元格范围，跳过")
                continue
            if cell_range not in cell_comments:
                cell_comments[cell_range] = []
            cell_comments[cell_range].append(comment)
            
        for cell_range, comments in cell_comments.items():
            if ":" not in cell_range:
                cell_range = f"{cell_range}:{cell_range}"
            original_text = processor.feishu_sheet.get_cell_value(sheet_token, sheet_id, cell_range)
            if original_text is None:
                logger.info(f"无法读取单元格 {cell_range} 的内容，跳过")
                continue
            logger.info(f"处理单元格 {cell_range}: {original_text[:50]}...")
            logger.info(f"找到 {len(comments)} 条相关评论")
            valid_comments = [c for c in comments if c.get("text")]
            if valid_comments:
                comment = valid_comments[0]
                revision_result = call_revision_api(original_text, original_text, comment, ProductHighlights)
                if not revision_result:
                    logger.info("改稿失败，跳过此单元格")
                    continue
                update_success = processor.feishu_sheet.update_cell_value(sheet_token, sheet_id, cell_range, revision_result)
                if not update_success:
                    logger.info("更新单元格失败，跳过")
                    continue
                logger.info(f"单元格更新成功: {revision_result[:50]}...")
            else:
                logger.info("没有找到有效评论（有文本内容），跳过此单元格")
                
        logger.info("删除所有评论...")
        delete_success = processor.delete_all_comments(sheet_token)
        logger.info("评论删除成功" if delete_success else "评论删除失败")
        logger.info("\n电子表格处理完成")
    except Exception as e:
        logger.error(f"处理电子表格过程中发生错误: {str(e)}")
        import traceback
        traceback.print_exc()

async def process_doc_document(processor: FeishuSheetCommentProcessor, doc_token: str, file_type: str, ProductHighlights: Optional[str]):
    """处理文档类型文档 (doc/docx)"""
    try:
        # 获取文档评论
        all_comments = processor.get_doc_comments(doc_token, file_type)
        logger.info(f"找到 {len(all_comments)} 条评论")
        if not all_comments:
            logger.info("该文档没有评论，跳过")
            return
            
        # 获取文档内容
        doc_content, all_block = processor.get_all_block_content(doc_token) 
        logger.info(f"文档内容长度: {len(doc_content) if doc_content else 0} 字符")
        
        
        # 构造评论字典，格式为{"quote1":"", "text1": "", "quote2":"", "text2":""}
        comments_dict = {}
        for i, comment in enumerate(all_comments):
            quote_key = f"quote{i+1}"
            text_key = f"text{i+1}"
            comments_dict[quote_key] = comment.get("quote", "")
            comments_dict[text_key] = comment.get("text", "")
        
        # 调用专门处理文档的改稿API
        revision_result = call_revision_api_doc(doc_content, comments_dict, ProductHighlights)
        if not revision_result:
            logger.info("改稿失败，跳过此文档")
        else:
            # 使用enumerate迭代all_block，根据block_type决定是否删除
            delete_index = -1
            for i, block in enumerate(all_block):
                block_type = block.get("block_type")
                # 如果block_type不在指定范围内，则记录索引并跳出循环
                if block_type not in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]:
                    delete_index = i - 1
                    break
            else:
                # 如果循环正常结束（所有block_type都在指定范围内），则使用最后一个索引
                delete_index = len(all_block[0].get("children",[]))
            
            # 只有当delete_index >= 0时才调用delete_doc_content
            if delete_index >= 0:
                processor.delete_doc_content(doc_token, doc_token, 0, delete_index)
            processor.create_block(doc_token, all_block[0].get("block_id", doc_token), 0, revision_result)
                
        logger.info("删除所有文档评论...")
        delete_success = processor.delete_doc_comments(doc_token, file_type)
        logger.info("文档评论删除成功" if delete_success else "文档评论删除失败")
        logger.info("\n文档处理完成")
    except Exception as e:
        logger.error(f"处理文档过程中发生错误: {str(e)}")
        import traceback
        traceback.print_exc()
        
# ------------------------------
# 关键改动3：启动服务无交互（用环境变量动态获取端口，避免硬编码和手动输入）
# ------------------------------
if __name__ == "__main__":
    # 从环境变量读取端口（云服务器部署时通过Supervisor配置注入，无需手动输入）
    # 默认值8850（对应你计划的端口，避免用8002硬编码）
    port = int(os.environ.get("API_PORT", 8850))
    # 直接启动服务，无任何等待输入的逻辑
    uvicorn.run(app, host="0.0.0.0", port=port)